package nr.co.mhgames.polyanim;

import java.util.HashMap;

/**
 * Key frame, consisting of several polygon groups. Animation consists of
 * several key frames along the defined timeline. Each key frame can contain any
 * amount of groups.
 * <p>
 * The groups that are in two or more frames, should be given a common name. For
 * example, for a character walking animation the characters head would be a
 * group and have name <code>"head"</code>. To make the head move during the
 * animation, all of the key frames contain a group named <code>"head"</code> in
 * different position. It's important to note that group named <code>"X"</code>
 * must have the same amount of polygons in every frame next to each other. So
 * if in frame <b>N</b> group <code>"X"</code> contains 5 polygons, group
 * <code>"X"</code> in frames <b>N-1</b> and <b>N+1</b> must contain 5 polygons
 * as well (<b>N-1</b> requirement is since the animation can be played
 * backwards as well). Naturally the transformations and polygon forms can be
 * altered for each key frame.
 * <p>
 * Groups that are not in the next frame will remain static and visible only for
 * the current frame. A group can be hidden and shown through
 * {@link Group#setVisible(boolean)} for others.
 * 
 * @author Mika Halttunen
 */
public class KeyFrame {
	/** Map of the polygon groups for the frame */
	protected final HashMap<String, Group> groups;

	/** Default constructor */
	public KeyFrame() {
		groups = new HashMap<String, Group>();
	}

	/**
	 * Constructor
	 * 
	 * @param groups
	 *            Map of groups
	 */
	public KeyFrame(HashMap<String, Group> groups) {
		this.groups = groups;
	}

	/**
	 * Another constructor
	 * 
	 * @param groups
	 *            Array of groups
	 */
	public KeyFrame(Group... groups) {
		this();
		this.groups.clear();
		for (Group g: groups) {
			this.groups.put(g.getName(), g);
		}
	}


	/**
	 * Add a group to the key frame. If group by the same name already exists,
	 * it is replaced with the new group.
	 * 
	 * @param group
	 *            Group to add
	 */
	public void addGroup(Group group) {
		groups.put(group.getName(), group);
	}

	/**
	 * Return a list of the groups in the frame.
	 * 
	 * @return Map of groups
	 */
	public HashMap<String, Group> getGroups() {
		return groups;
	}

	/**
	 * Return the group by given name. If there is no group with the given name,
	 * <code>null</code> is returned.
	 * 
	 * @param name
	 *            Group name
	 * @return Group reference, or <code>null</code> if not found
	 */
	public Group getGroup(String name) {
		return groups.get(name);
	}

	/** Return the group count */
	public int getGroupCount() {
		return groups.size();
	}

	/**
	 * Interpolate between two key frames A and B. Both frames can have
	 * different set of polygon groups. However, for the interpolation to occur,
	 * the frames must have common groups (i.e. groups that have a common name).
	 * It's required for two groups having the same name to have exactly the
	 * same amount of polygons, so group <code>"X"</code> of A's polygon #1
	 * corresponds to the polygon #1 in group <code>"X"</code> of B, etc. That
	 * doesn't of course mean the group polygons must be in the same locations!
	 * 
	 * @param a
	 *            Key frame A
	 * @param b
	 *            Key frame B
	 * @param t
	 *            Interpolation factor. Zero means the resulting frame is A,
	 *            while one means the resulting frame is B. Anything between is
	 *            interpolated.
	 * @return An interpolated key frame between A and B.
	 */
	public static KeyFrame interpolate(KeyFrame a, KeyFrame b, float t) {
		if (a == b) {
			// It's the same frame, don't interpolate
			return a;
		}

		if (t <= 0) {
			// Don't interpolate at all (result is A)
			return a;
		} else if (t >= 1.0f) {
			// Interpolate fully (result is B)
			return b;
		}

		// Reserve space for the new key frame
		HashMap<String, Group> groups = new HashMap<String, Group>(a.getGroupCount());
		KeyFrame frame = new KeyFrame(groups);

		// Interpolate the polygon groups
		for (Group gA: a.getGroups().values()) {
			// Check if B contains the same group
			String name = gA.getName();
			Group gB = b.getGroup(name);
			if (gB != null) // Found in B
				groups.put(name, Group.interpolate(gA, gB, t));
		}

		return frame;
	}

	@Override
	public String toString() {
		return String.format("KeyFrame: %d groups, %s", getGroupCount(), super.toString());
	}
}
